package com.xxx.base.dao;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * Created by xnat on 17/8/9.
 */
@Transactional(readOnly = true)
public class BaseRepo<T, ID extends Serializable> {
    protected final Logger log = LoggerFactory.getLogger(getClass());
    @PersistenceContext
    protected EntityManager              em;
    protected Class<T>                   domainType;
    private SimpleJpaRepository<T, ID>   jpaRepo;


    @PostConstruct
    public void init() throws Exception {
        Type genType = getClass().getGenericSuperclass();
        if (genType instanceof ParameterizedType) {
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            if (params[0] instanceof Class) {
                domainType = (Class) params[0];
            }
        }
        Objects.requireNonNull(domainType, "domainType is null");
        jpaRepo = new SimpleJpaRepository(domainType, em);
    }


    @Transactional
    public <S extends T> S saveOrUpdate(S entity) {
        return jpaRepo.save(entity);
    }


    @Transactional
    public <S extends T> void saveOrUpdate(Collection<S> es) {
        if (es == null || es.isEmpty()) return;
        es.forEach(e -> jpaRepo.save(e));
    }


    @Transactional
    public <S extends T> S insert(S entity) {
//        if (entity.getId() == null) {
//            entity.setId((long) UUID.randomUUID().toString().hashCode());
//        }
        em.persist(entity);
        return entity;
    }


    public <S extends T> void insert(Collection<S> entities) {
        if (entities == null || entities.isEmpty()) return;
        entities.forEach(e -> insert(e));
    }


    @Transactional
    public <S extends T> S update(S entity) {
        return em.merge(entity);
    }


    public T findOne(ID id) {
        return em.find(domainType, id);
    }


    @Transactional
    public int updateSql(String sql, Object...params) {
        Query q = em.createNativeQuery(sql);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                q.setParameter(i+1, params[i]);
            }
        }
        return q.executeUpdate();
    }


    @Transactional
    public int updateHql(String hql, Object...params) {
        Query q = em.createQuery(hql);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                q.setParameter(i, params[i]);
            }
        }
        return q.executeUpdate();
    }


    public T findOne(Query query) {
        try {
            return (T) query.setMaxResults(1).getSingleResult();
        } catch (NoResultException e) {
        }
        return null;
    }


    @Transactional
    public boolean delete(ID id) {
        return em.createQuery("delete from " + domainType.getName() + " where id=:id")
                .setParameter("id", id)
                .executeUpdate() > 0;
    }


    @Transactional
    public void delete(T entity) {
        jpaRepo.delete(entity);
    }


    public List<T> findAll(Iterable<ID> ids) {
        return jpaRepo.findAllById(ids);
    }


    public List<T> findAll(Query query) {
        return query.getResultList();
    }


    public List<T> findAll() {
        return jpaRepo.findAll();
    }


    public long count() {
        return jpaRepo.count();
    }
}
